gRPC 的认证
gRPC 默认提供了两种认证方式:
- 基于SSL/TLS认证方式
- 远程调用认证方式
两种方式可以混合使用
证书制作
以下实现 TLS 认证机制
首先是制作私钥 (.key)
# 使用 RSA 算法生成私钥
openssl genrsa -out server.key 2048
# or
# 使用 ECDSA 算法生成私钥
openssl ecparam -genkey -name secp384r1 -out server.key
RSA 和 ECDSA 是最广泛使用的数字签名算法
RSA 是目前计算机密码学中最经典算法,也是目前为止使用最广泛的数字签名算法,RSA 数字签名算法的密钥实现与 RSA 的加密算法是一样的,算法的名称都叫 RSA。密钥的产生和转换都是一样的,包括在售的所有 SSL 数字证书、代码签名证书、文档签名以及邮件签名大多都采用 RSA 算法进行加密。
ECDSA:Elliptic Curve Digital Signature Algorithm(椭圆曲线签名算法)也是一种常用的签名算法,它的特点是可以从私钥推出公钥。比特币的签名算法就采用了 ECDSA 算法,使用标准椭圆曲线 secp256k1。
ECC 与 RSA 相比,有以下的优点: a. 相同密钥长度下,安全性能更高,如160位ECC已经与1024位RSA、DSA有相同的安全强度。 b. 计算量小,处理速度快,在私钥的处理速度上(解密和签名),ECC远 比RSA、DSA快得多。 c. 存储空间占用小 ECC的密钥尺寸和系统参数与RSA、DSA相比要小得多, 所以占用的存储空间小得多。 d. 带宽要求低使得ECC具有广泛得应用前景。
更多细节参考 数字签名算法介绍和区别
然后是对上面生成的私钥生成证书
openssl req -new -x509 -sha256 -key server.key -out server.pem -days 3650
自定义信息
-----
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:XxXx
Locality Name (eg, city) []:XxXx
Organization Name (eg, company) [Internet Widgits Pty Ltd]:XX Co. Ltd
Organizational Unit Name (eg, section) []:Dev
Common Name (e.g. server FQDN or YOUR name) []:server name
Email Address []:xxx@xxx.com
续上:grpc 中 TLS 认证证书问题
golang 1.15+ 版本上,用 gRPC 通过 TLS 实现数据传输加密时,会报错证书的问题
panic: rpc error: code = Unavailable desc = connection error: desc = "transport: authentication handshake failed: x509: certificate
is not valid for any names, but wanted to match localhost"
造成的原因是因为我们用的证书,并没有开启SAN扩展(默认是没有开启SAN扩展)所生成的,
导致客户端和服务端无法建立连接
使用开启扩展 SAN 的证书
什么是 SAN?
SAN(Subject Alternative Name) 是 SSL 标准 x509 中定义的一个扩展。使用了 SAN 字段的 SSL 证书,可以扩展此证书支持的域名,使得一个证书可以支持多个不同域名的解析。
生成 CA 根证书
新建 ca.conf
写入下列内容
[ req ]
default_bits = 4096
distinguished_name = req_distinguished_name
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_default = CN
stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = GuangDong
localityName = Locality Name (eg, city)
localityName_default = Shenzhen
organizationName = Organization Name (eg, company)
organizationName_default = Sheld
commonName = Common Name (e.g. server FQDN or YOUR name)
commonName_max = 64
commonName_default = Ted CA Test
再去生成 ca 秘钥,得到 ca.key
openssl genrsa -out ca.key 4096
生成 ca 证书签发请求,得到 ca.csr
openssl req \
-new \
-sha256 \
-out ca.csr \
-key ca.key \
-config ca.conf
然后一路回车就行了
最后生成 ca 根证书,得到 ca.crt
openssl x509 \
-req \
-days 3650 \
-in ca.csr \
-signkey ca.key \
-out ca.crt
生成终端用户证书
准备配置文件,得到 server.conf
写入内容如下:
[ req ]
default_bits = 2048
distinguished_name = req_distinguished_name
req_extensions = req_ext
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_default = CN
stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = GuangDong
localityName = Locality Name (eg, city)
localityName_default = GuangZhou
organizationName = Organization Name (eg, company)
organizationName_default = Sheld
commonName = alsritter
commonName_max = 64
# commonName_default 这个配置项,如果有域名就写域名,本地就 localhost
commonName_default = alsritter.icu
[ req_ext ]
subjectAltName = @alt_names
[alt_names]
DNS.1 = alsritter.icu
IP = 127.0.0.1
生成秘钥,得到 server.key
openssl genrsa -out server.key 2048
生成证书签发请求,得到 server.csr
openssl req \
-new \
-sha256 \
-out server.csr \
-key server.key \
-config server.conf
shell 交互时一路回车就行
用 CA 证书生成终端用户证书,得到 server.crt
openssl x509 \
-req \
-days 3650 \
-CA ca.crt \
-CAkey ca.key \
-CAcreateserial \
-in server.csr \
-out server.pem \
-extensions req_ext \
-extfile server.conf
现在证书已经生成完毕, server.pem 和 server.key 就是我们需要的证书和密钥
服务端代码:
creds, err := credentials.NewServerTLSFromFile("./keys/server.pem", "./keys/server.key")
客户端代码:
creds, err := credentials.NewClientTLSFromFile("./keys/server.pem", "alsritter.icu")
初始化环境
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go get -u github.com/golang/protobuf/protoc-gen-go;
创建 proto/helloworld.proto 文件
syntax = "proto3"; // 指定proto版本
package hello; // 指定包名
option go_package = "./proto"; // 这个路径必须包含一个 '.' 或者 '/'
// 定义Hello服务
service Hello {
// 定义SayHello方法
rpc SayHello(HelloRequest) returns (HelloResponse) {}
}
// HelloRequest 请求结构
message HelloRequest {
string name = 1;
}
// HelloResponse 响应结构
message HelloResponse {
string message = 1;
}
编译生成 go 文件
protoc -I . --go_out=plugins=grpc:. proto/*.proto
编写服务端代码
server/main.go
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/grpclog"
"net"
pb "grpc-auth/proto"
)
const (
// Address gRPC 服务地址
Address = "127.0.0.1:50052"
)
// 定义helloService并实现约定的接口
type helloService struct{}
var HelloService = helloService{}
func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
resp := new(pb.HelloResponse)
resp.Message = "Hello " + in.Name + "."
return resp, nil
}
func main() {
listen, err := net.Listen("tcp", Address)
if err != nil {
grpclog.Fatalf("failed to listen: %v", err)
}
// TLS 认证
creds, err := credentials.NewServerTLSFromFile("../../keys/server.pem", "../../keys/server.key")
if err != nil {
grpclog.Fatalf("Failed to generate credentials %v", err)
}
// 实例化grpc Server, 并开启TLS认证
s := grpc.NewServer(grpc.Creds(creds))
// 注册HelloService
pb.RegisterHelloServer(s, HelloService)
fmt.Println("Listen on " + Address + " with TLS")
s.Serve(listen)
}
服务端在实例化 grpc Server 时,可配置多种选项,TLS 认证是其中之一
客户端添加TLS认证
client/main.go
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/grpclog"
pb "grpc-auth/proto"
)
const (
// Address gRPC服务地址
Address = "127.0.0.1:50052"
)
func main() {
// TLS连接
creds, err := credentials.NewClientTLSFromFile("../../keys/server.pem", "alsritter.icu")
if err != nil {
grpclog.Fatalf("Failed to create TLS credentials %v", err)
}
conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds))
if err != nil {
grpclog.Fatalln(err)
}
defer conn.Close()
// 初始化客户端
c := pb.NewHelloClient(conn)
// 调用方法
reqBody := new(pb.HelloRequest)
reqBody.Name = "gRPC"
r, err := c.SayHello(context.Background(), reqBody)
if err != nil {
grpclog.Fatalln(err)
}
fmt.Println(r.Message)
}